I’m fitting cublic regression splines to the cumulative number of deaths, restricted to be monotonically increasing. To get indication of the uncertainty of the models, I’m bootstrapping 1,000 models.
library(tidyverse)
library(gghighlight)
library(httr)
library(utils)
library(lubridate)
library(mgcv)
library(itsadug)
library(ggthemes)
library(magrittr)
library(boot)
library(stringi)
I’m getting the data from ECDC https://www.ecdc.europa.eu/en/publications-data/download-todays-data-geographic-distribution-covid-19-cases-worldwide
# their api for this seems to be changing daily
GET("https://opendata.ecdc.europa.eu/covid19/casedistribution/csv", authenticate(":", ":", type="ntlm"), write_disk(tf <- tempfile(fileext = ".csv")))
Response [https://opendata.ecdc.europa.eu/covid19/casedistribution/csv/]
Date: 2020-04-02 14:29
Status: 200
Content-Type: application/octet-stream
Size: 426 kB
<ON DISK> /var/folders/mb/20kpds6n56s444vgrwpfc7km0000gn/T//Rtmp4VwwHr/file1621944b08c83.csv
#read the Dataset sheet into “R”. The dataset will be called "data".
data <- read.csv(tf)
This is the code for fitting a spline that is monotonic.
# https://gist.github.com/jnpaulson/c47f9bd3246f1121ad3a911e5f707355
mspline<-function(x,y,k=10,lower=NA,upper=NA){
#fits a monotonic spline to data
#small values of k= more smoothing (flatter curves)
#large values of k= more flexible (wiggly curves)
#k is related to effective degrees of freedom and number of knots
#use unconstrained gam to get rough parameter estimates
#lower, upper optional bounds on the function
#basically a slight modification of an example in the mgcv::pcls documentation
dat<-data.frame(x=x,y=y)
init_gam <- gam(y~s(x,k=k,bs="cr"))
# Create Design matrix, constraints etc. for monotonic spline....
sm <- smoothCon(s(x,k=k,bs="cr"),dat,knots=NULL)[[1]]
mc <- mono.con(sm$xp,lower=lower,upper=upper) # monotonicity constraints
M <- list(X=sm$X, y=y, #design matrix, outcome
C=matrix(0,0,0), #equality constraints (none)
Ain=mc$A, bin=mc$b, #inequality constraints
sp=init_gam$sp, p=sm$xp, #initial guesses for param estimates
S=sm$S, #smoothness penalty matrix
w=y*0+1, off=0 #weights, offset
)
#fit spine using penalized constrained least squares
p<-pcls(M)
return(list(sm=sm,p=p))
}
predict.mspline<-function(msp,x){
#using the monotone spline msp, predict values for the vector x
Predict.matrix(msp$sm,data.frame(x=x))%*%msp$p
}
Since I’m measuring time for each country according to how many days since 10 deaths, it would be inappropriate to use the same number of knots for each country’s data. Also, since bootstrapping produces a different number of unique x values in each replciate, I’m setting the number of knots to half the number of unique x values per replicate. This produces some very different smooths across bootstraps, but since I’m overplotting all the replicates, I’m sticking with it as an indicator of the fragility of the estimates.
do_msmooth <- function(data, inds, pred_x){
nunique <- length(unique(data$ndays[inds]))
knots = floor(nunique/2)
M <- mspline(data$ndays[inds], log(data$total_deaths[inds]),
k = ifelse(knots < 4, 4, knots))
pred <- predict.mspline(M, pred_x)
return(pred)
}
I’m going to focus on modellable_data, which is countries which have had at least 10 total deaths, and at least 15 days worth of data from their 10th death.
# the cedilla in Curaçao is messing things up. I'll fix it at some point
data %>%
filter(stri_enc_isutf8(countriesAndTerritories))%>% # A hack for now :(
mutate(countriesAndTerritories = tolower(countriesAndTerritories),
date = dmy(dateRep))%>%
group_by(countriesAndTerritories)%>%
mutate(total = sum(deaths)) %>%
ungroup() %>%
mutate(country = reorder(countriesAndTerritories, -total, min)) %>%
group_by(country)%>%
arrange(date) %>%
mutate(total_deaths = cumsum(deaths)) ->data_proc
data_proc %>%
filter(total_deaths >=10)%>%
mutate(ndays = 1:n())%>%
filter(max(ndays) >= 15) %>%
group_by(country)%>%
nest() %>%
arrange(country) %>%
ungroup()%>%
filter(country %in% country[1:10] | country %in% c("japan", "china", "south_korea")) %>%
unnest(data) ->modellable_data
A table of feaths as of the most recent date.
data_proc%>%
mutate(date = dmy(dateRep)) %>%
group_by(country)%>%
slice(n())%>%
select(date, country, deaths, total_deaths)%>%
arrange(-total_deaths)
I’m also overplotting dates of national lockdowns.
lockdowns <- tribble(
~country, ~date,
"italy", ymd("2020-3-9"),
"spain", ymd("2020-3-14"),
"china", ymd("2020-1-23"),
"france", ymd("2020-3-17"),
"united_kingdom", ymd("2020-3-23")
)%>%
left_join(modellable_data) %>%
mutate(country = factor(country),
country = reorder(country, -total, mean))
Here is the overall cumulative deaths, log-scale
modellable_data %>%
ggplot(aes(ndays, total_deaths, color = country))+
geom_line(size = 1)+
gghighlight(use_direct_label = F)+
geom_vline(data = lockdowns, aes(xintercept = ndays),
linetype = 1,
color = "grey30")+
geom_vline(data = lockdowns, aes(xintercept = ndays+14),
linetype = 3,
color = "grey30")+
facet_wrap(~country)+
scale_color_discrete(l = 50, guide = "none")+
scale_y_log10("cumulative deaths (log scale)")+
xlab("number of days since 10 deaths")+
theme_minimal()

This does the bootstrapping.
modellable_data %>%
group_by(country) %>%
nest()%>%
mutate(boot = map(data, ~boot(.x,
statistic = do_msmooth,
R = 1000,
stype = "i",
pred_x = 1:nrow(.x))),
boot_df = map(boot, ~data.frame(t(.x$t)) %>%
rownames_to_column("ndays") %>%
gather("draw", "value", -1)))%>%
unnest(boot_df)%>%
group_by(country, draw) %>%
mutate(diff = value-lag(value),
rate = exp(diff)) %>%
select("country", "ndays", "draw", "value", "diff", "rate")->bootstraps
Just to check that the models aren’t over- or under- smoothing the data.
bootstraps %>%
ggplot(aes(as.numeric(ndays), exp(value), color = country))+
geom_line(aes(group = draw), alpha = 0.02)+
geom_point(data = modellable_data, aes(y = total_deaths),
color = "black", size = 0.5)+
scale_y_log10()+
facet_wrap(~country)+
xlab("ndays since 10 total deaths")+
ylab("cumulative deaths (log scale)")+
scale_color_discrete(guide = "none", l = 50)+
theme_minimal()

This is a plot of daily estimated rate of increase of total deaths (down is good).
This is a plot of daily estimated ‘doubling rate’ (up is good).
LS0tCnRpdGxlOiAiRGVhdGggUmF0ZSBNb2RlbGxpbmciCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0aGVtZTogeWV0aQpkYXRlOiAiYHIgbHVicmlkYXRlOjpub3coKWAgIgphdXRob3I6ICJKb3NlZiBGcnVlaHdhbGQiCi0tLQoKSSdtIGZpdHRpbmcgY3VibGljIHJlZ3Jlc3Npb24gc3BsaW5lcyB0byB0aGUgY3VtdWxhdGl2ZSBudW1iZXIgb2YgZGVhdGhzLCByZXN0cmljdGVkIHRvIGJlIG1vbm90b25pY2FsbHkgaW5jcmVhc2luZy4gVG8gZ2V0IGluZGljYXRpb24gb2YgdGhlIHVuY2VydGFpbnR5IG9mIHRoZSBtb2RlbHMsIEknbSBib290c3RyYXBwaW5nIDEsMDAwIG1vZGVscy4KCgoKYGBge3IgbGlicmFyaWVzfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ2hpZ2hsaWdodCkKbGlicmFyeShodHRyKQpsaWJyYXJ5KHV0aWxzKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShtZ2N2KQpsaWJyYXJ5KGl0c2FkdWcpCmxpYnJhcnkoZ2d0aGVtZXMpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoYm9vdCkKbGlicmFyeShzdHJpbmdpKQpgYGAKCkknbSBnZXR0aW5nIHRoZSBkYXRhIGZyb20gRUNEQyBbaHR0cHM6Ly93d3cuZWNkYy5ldXJvcGEuZXUvZW4vcHVibGljYXRpb25zLWRhdGEvZG93bmxvYWQtdG9kYXlzLWRhdGEtZ2VvZ3JhcGhpYy1kaXN0cmlidXRpb24tY292aWQtMTktY2FzZXMtd29ybGR3aWRlXShodHRwczovL3d3dy5lY2RjLmV1cm9wYS5ldS9lbi9wdWJsaWNhdGlvbnMtZGF0YS9kb3dubG9hZC10b2RheXMtZGF0YS1nZW9ncmFwaGljLWRpc3RyaWJ1dGlvbi1jb3ZpZC0xOS1jYXNlcy13b3JsZHdpZGUpCgoKCmBgYHtyIGdldF9lY2RjX2RhdGF9CiMgdGhlaXIgYXBpIGZvciB0aGlzIHNlZW1zIHRvIGJlIGNoYW5naW5nIGRhaWx5CgpHRVQoImh0dHBzOi8vb3BlbmRhdGEuZWNkYy5ldXJvcGEuZXUvY292aWQxOS9jYXNlZGlzdHJpYnV0aW9uL2NzdiIsIGF1dGhlbnRpY2F0ZSgiOiIsICI6IiwgdHlwZT0ibnRsbSIpLCB3cml0ZV9kaXNrKHRmIDwtIHRlbXBmaWxlKGZpbGVleHQgPSAiLmNzdiIpKSkKCiNyZWFkIHRoZSBEYXRhc2V0IHNoZWV0IGludG8g4oCcUuKAnS4gVGhlIGRhdGFzZXQgd2lsbCBiZSBjYWxsZWQgImRhdGEiLgpkYXRhIDwtIHJlYWQuY3N2KHRmKQpgYGAKClRoaXMgaXMgdGhlIGNvZGUgZm9yIGZpdHRpbmcgYSBzcGxpbmUgdGhhdCBpcyBtb25vdG9uaWMuCgpgYGB7cn0KIyBodHRwczovL2dpc3QuZ2l0aHViLmNvbS9qbnBhdWxzb24vYzQ3ZjliZDMyNDZmMTEyMWFkM2E5MTFlNWY3MDczNTUKbXNwbGluZTwtZnVuY3Rpb24oeCx5LGs9MTAsbG93ZXI9TkEsdXBwZXI9TkEpewogICNmaXRzIGEgbW9ub3RvbmljIHNwbGluZSB0byBkYXRhCiAgI3NtYWxsIHZhbHVlcyBvZiBrPSBtb3JlIHNtb290aGluZyAoZmxhdHRlciBjdXJ2ZXMpCiAgI2xhcmdlIHZhbHVlcyBvZiBrPSBtb3JlIGZsZXhpYmxlICh3aWdnbHkgY3VydmVzKQogICNrIGlzIHJlbGF0ZWQgdG8gZWZmZWN0aXZlIGRlZ3JlZXMgb2YgZnJlZWRvbSBhbmQgbnVtYmVyIG9mIGtub3RzCiAgI3VzZSB1bmNvbnN0cmFpbmVkIGdhbSB0byBnZXQgcm91Z2ggcGFyYW1ldGVyIGVzdGltYXRlcwogICNsb3dlciwgdXBwZXIgb3B0aW9uYWwgYm91bmRzIG9uIHRoZSBmdW5jdGlvbgogICNiYXNpY2FsbHkgYSBzbGlnaHQgbW9kaWZpY2F0aW9uIG9mIGFuIGV4YW1wbGUgaW4gdGhlIG1nY3Y6OnBjbHMgZG9jdW1lbnRhdGlvbgogIGRhdDwtZGF0YS5mcmFtZSh4PXgseT15KQogIGluaXRfZ2FtIDwtIGdhbSh5fnMoeCxrPWssYnM9ImNyIikpCiAgIyBDcmVhdGUgRGVzaWduIG1hdHJpeCwgY29uc3RyYWludHMgZXRjLiBmb3IgbW9ub3RvbmljIHNwbGluZS4uLi4KICBzbSA8LSBzbW9vdGhDb24ocyh4LGs9ayxicz0iY3IiKSxkYXQsa25vdHM9TlVMTClbWzFdXQogIG1jIDwtIG1vbm8uY29uKHNtJHhwLGxvd2VyPWxvd2VyLHVwcGVyPXVwcGVyKSAjIG1vbm90b25pY2l0eSBjb25zdHJhaW50cwogIE0gPC0gbGlzdChYPXNtJFgsIHk9eSwgI2Rlc2lnbiBtYXRyaXgsIG91dGNvbWUKICAgICAgICAgICAgQz1tYXRyaXgoMCwwLDApLCAjZXF1YWxpdHkgY29uc3RyYWludHMgKG5vbmUpCiAgICAgICAgICAgIEFpbj1tYyRBLCBiaW49bWMkYiwgI2luZXF1YWxpdHkgY29uc3RyYWludHMKICAgICAgICAgICAgc3A9aW5pdF9nYW0kc3AsIHA9c20keHAsICNpbml0aWFsIGd1ZXNzZXMgZm9yIHBhcmFtIGVzdGltYXRlcwogICAgICAgICAgICBTPXNtJFMsICNzbW9vdGhuZXNzIHBlbmFsdHkgbWF0cml4CiAgICAgICAgICAgIHc9eSowKzEsIG9mZj0wICN3ZWlnaHRzLCBvZmZzZXQKICAgICAgICAgICAgKQogICNmaXQgc3BpbmUgdXNpbmcgcGVuYWxpemVkIGNvbnN0cmFpbmVkIGxlYXN0IHNxdWFyZXMKICBwPC1wY2xzKE0pCiAgcmV0dXJuKGxpc3Qoc209c20scD1wKSkKfQoKcHJlZGljdC5tc3BsaW5lPC1mdW5jdGlvbihtc3AseCl7CiAgI3VzaW5nIHRoZSBtb25vdG9uZSBzcGxpbmUgbXNwLCBwcmVkaWN0IHZhbHVlcyBmb3IgdGhlIHZlY3RvciB4CiAgUHJlZGljdC5tYXRyaXgobXNwJHNtLGRhdGEuZnJhbWUoeD14KSklKiVtc3AkcAp9CgpgYGAKCgpTaW5jZSBJJ20gbWVhc3VyaW5nIHRpbWUgZm9yIGVhY2ggY291bnRyeSBhY2NvcmRpbmcgdG8gaG93IG1hbnkgZGF5cyBzaW5jZSAxMCBkZWF0aHMsIGl0IHdvdWxkIGJlIGluYXBwcm9wcmlhdGUgdG8gdXNlIHRoZSBzYW1lIG51bWJlciBvZiBrbm90cyBmb3IgZWFjaCBjb3VudHJ5J3MgZGF0YS4gQWxzbywgc2luY2UgYm9vdHN0cmFwcGluZyBwcm9kdWNlcyBhIGRpZmZlcmVudCBudW1iZXIgb2YgdW5pcXVlIHggdmFsdWVzIGluIGVhY2ggcmVwbGNpYXRlLCBJJ20gc2V0dGluZyB0aGUgbnVtYmVyIG9mIGtub3RzIHRvIGhhbGYgdGhlIG51bWJlciBvZiB1bmlxdWUgeCB2YWx1ZXMgcGVyIHJlcGxpY2F0ZS4gVGhpcyBwcm9kdWNlcyBzb21lIHZlcnkgZGlmZmVyZW50IHNtb290aHMgYWNyb3NzIGJvb3RzdHJhcHMsIGJ1dCBzaW5jZSBJJ20gb3ZlcnBsb3R0aW5nIGFsbCB0aGUgcmVwbGljYXRlcywgSSdtIHN0aWNraW5nIHdpdGggaXQgYXMgYW4gaW5kaWNhdG9yIG9mIHRoZSBmcmFnaWxpdHkgb2YgdGhlIGVzdGltYXRlcy4KCmBgYHtyfQpkb19tc21vb3RoIDwtIGZ1bmN0aW9uKGRhdGEsIGluZHMsIHByZWRfeCl7CiAgbnVuaXF1ZSA8LSBsZW5ndGgodW5pcXVlKGRhdGEkbmRheXNbaW5kc10pKQogIGtub3RzID0gZmxvb3IobnVuaXF1ZS8yKQogIE0gPC0gbXNwbGluZShkYXRhJG5kYXlzW2luZHNdLCBsb2coZGF0YSR0b3RhbF9kZWF0aHNbaW5kc10pLCAKICAgICAgICAgICAgICAgayA9IGlmZWxzZShrbm90cyA8IDQsIDQsIGtub3RzKSkKICBwcmVkIDwtIHByZWRpY3QubXNwbGluZShNLCBwcmVkX3gpCiAgcmV0dXJuKHByZWQpCn0KYGBgCgoKSSdtIGdvaW5nIHRvIGZvY3VzIG9uIGBtb2RlbGxhYmxlX2RhdGFgLCB3aGljaCBpcyBjb3VudHJpZXMgd2hpY2ggaGF2ZSBoYWQgYXQgbGVhc3QgMTAgdG90YWwgZGVhdGhzLCBhbmQgYXQgbGVhc3QgMTUgZGF5cyB3b3J0aCBvZiBkYXRhIGZyb20gdGhlaXIgMTB0aCBkZWF0aC4KCmBgYHtyfQojIHRoZSBjZWRpbGxhIGluIEN1cmHDp2FvIGlzIG1lc3NpbmcgdGhpbmdzIHVwLiBJJ2xsIGZpeCBpdCBhdCBzb21lIHBvaW50CmRhdGEgJT4lCiAgZmlsdGVyKHN0cmlfZW5jX2lzdXRmOChjb3VudHJpZXNBbmRUZXJyaXRvcmllcykpJT4lICMgQSBoYWNrIGZvciBub3cgOigKICBtdXRhdGUoY291bnRyaWVzQW5kVGVycml0b3JpZXMgPSB0b2xvd2VyKGNvdW50cmllc0FuZFRlcnJpdG9yaWVzKSwKICAgICAgICAgZGF0ZSA9IGRteShkYXRlUmVwKSklPiUKICBncm91cF9ieShjb3VudHJpZXNBbmRUZXJyaXRvcmllcyklPiUKICBtdXRhdGUodG90YWwgPSBzdW0oZGVhdGhzKSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShjb3VudHJ5ID0gIHJlb3JkZXIoY291bnRyaWVzQW5kVGVycml0b3JpZXMsIC10b3RhbCwgbWluKSkgJT4lCiAgZ3JvdXBfYnkoY291bnRyeSklPiUKICBhcnJhbmdlKGRhdGUpICU+JQogIG11dGF0ZSh0b3RhbF9kZWF0aHMgPSBjdW1zdW0oZGVhdGhzKSkgLT5kYXRhX3Byb2MKCmRhdGFfcHJvYyAlPiUKICBmaWx0ZXIodG90YWxfZGVhdGhzID49MTApJT4lCiAgbXV0YXRlKG5kYXlzID0gMTpuKCkpJT4lCiAgZmlsdGVyKG1heChuZGF5cykgPj0gMTUpICU+JQogIGdyb3VwX2J5KGNvdW50cnkpJT4lCiAgbmVzdCgpICU+JQogIGFycmFuZ2UoY291bnRyeSkgJT4lCiAgdW5ncm91cCgpJT4lCiAgZmlsdGVyKGNvdW50cnkgICVpbiUgY291bnRyeVsxOjEwXSB8IGNvdW50cnkgJWluJSBjKCJqYXBhbiIsICJjaGluYSIsICJzb3V0aF9rb3JlYSIpKSAlPiUKICB1bm5lc3QoZGF0YSkgLT5tb2RlbGxhYmxlX2RhdGEKYGBgCgoKQSB0YWJsZSBvZiBmZWF0aHMgYXMgb2YgdGhlIG1vc3QgcmVjZW50IGRhdGUuCmBgYHtyfQpkYXRhX3Byb2MlPiUKICBtdXRhdGUoZGF0ZSA9IGRteShkYXRlUmVwKSkgJT4lCiAgZ3JvdXBfYnkoY291bnRyeSklPiUKICBzbGljZShuKCkpJT4lCiAgc2VsZWN0KGRhdGUsIGNvdW50cnksIGRlYXRocywgdG90YWxfZGVhdGhzKSU+JQogIGFycmFuZ2UoLXRvdGFsX2RlYXRocykKYGBgCgoKCkknbSBhbHNvIG92ZXJwbG90dGluZyBkYXRlcyBvZiBuYXRpb25hbCBsb2NrZG93bnMuCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxvY2tkb3ducyA8LSB0cmliYmxlKAogIH5jb3VudHJ5LCB+ZGF0ZSwKICAiaXRhbHkiLCB5bWQoIjIwMjAtMy05IiksCiAgInNwYWluIiwgeW1kKCIyMDIwLTMtMTQiKSwKICAiY2hpbmEiLCB5bWQoIjIwMjAtMS0yMyIpLAogICJmcmFuY2UiLCB5bWQoIjIwMjAtMy0xNyIpLAogICJ1bml0ZWRfa2luZ2RvbSIsIHltZCgiMjAyMC0zLTIzIikKKSU+JQogIGxlZnRfam9pbihtb2RlbGxhYmxlX2RhdGEpICU+JQogIG11dGF0ZShjb3VudHJ5ID0gZmFjdG9yKGNvdW50cnkpLAogICAgICAgICBjb3VudHJ5ID0gcmVvcmRlcihjb3VudHJ5LCAtdG90YWwsIG1lYW4pKQpgYGAKCgpIZXJlIGlzIHRoZSBvdmVyYWxsIGN1bXVsYXRpdmUgZGVhdGhzLCBsb2ctc2NhbGUKYGBge3IgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9Cm1vZGVsbGFibGVfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKG5kYXlzLCB0b3RhbF9kZWF0aHMsIGNvbG9yID0gY291bnRyeSkpKwogIGdlb21fbGluZShzaXplID0gMSkrCiAgZ2doaWdobGlnaHQodXNlX2RpcmVjdF9sYWJlbCA9IEYpKwogIGdlb21fdmxpbmUoZGF0YSA9IGxvY2tkb3ducywgYWVzKHhpbnRlcmNlcHQgPSBuZGF5cyksIAogICAgICAgICAgICAgbGluZXR5cGUgPSAxLCAKICAgICAgICAgICAgIGNvbG9yID0gImdyZXkzMCIpKwogIGdlb21fdmxpbmUoZGF0YSA9IGxvY2tkb3ducywgYWVzKHhpbnRlcmNlcHQgPSBuZGF5cysxNCksIAogICAgICAgICAgICAgbGluZXR5cGUgPSAzLAogICAgICAgICAgICAgY29sb3IgPSAiZ3JleTMwIikrCiAgZmFjZXRfd3JhcCh+Y291bnRyeSkrCiAgc2NhbGVfY29sb3JfZGlzY3JldGUobCA9IDUwLCBndWlkZSA9ICJub25lIikrCiAgc2NhbGVfeV9sb2cxMCgiY3VtdWxhdGl2ZSBkZWF0aHMgKGxvZyBzY2FsZSkiKSsKICB4bGFiKCJudW1iZXIgb2YgZGF5cyBzaW5jZSAxMCBkZWF0aHMiKSsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpUaGlzIGRvZXMgdGhlIGJvb3RzdHJhcHBpbmcuCmBgYHtyfQptb2RlbGxhYmxlX2RhdGEgJT4lCiAgZ3JvdXBfYnkoY291bnRyeSkgJT4lCiAgbmVzdCgpJT4lCiAgbXV0YXRlKGJvb3QgPSBtYXAoZGF0YSwgfmJvb3QoLngsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhdGlzdGljID0gZG9fbXNtb290aCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUiA9IDEwMDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eXBlID0gImkiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRfeCA9IDE6bnJvdygueCkpKSwKICAgICAgICAgYm9vdF9kZiA9IG1hcChib290LCB+ZGF0YS5mcmFtZSh0KC54JHQpKSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICByb3duYW1lc190b19jb2x1bW4oIm5kYXlzIikgJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgZ2F0aGVyKCJkcmF3IiwgInZhbHVlIiwgLTEpKSklPiUKICB1bm5lc3QoYm9vdF9kZiklPiUKICBncm91cF9ieShjb3VudHJ5LCBkcmF3KSAlPiUKICBtdXRhdGUoZGlmZiA9IHZhbHVlLWxhZyh2YWx1ZSksCiAgICAgICAgIHJhdGUgPSBleHAoZGlmZikpICU+JQogIHNlbGVjdCgiY291bnRyeSIsICJuZGF5cyIsICJkcmF3IiwgInZhbHVlIiwgImRpZmYiLCAicmF0ZSIpLT5ib290c3RyYXBzCmBgYAoKCkp1c3QgdG8gY2hlY2sgdGhhdCB0aGUgbW9kZWxzIGFyZW4ndCBvdmVyLSBvciB1bmRlci0gc21vb3RoaW5nIHRoZSBkYXRhLgpgYGB7ciBtb2RlbF9zYW5pdHlfY2hlY2ssIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTV9CmJvb3RzdHJhcHMgJT4lCiAgZ2dwbG90KGFlcyhhcy5udW1lcmljKG5kYXlzKSwgZXhwKHZhbHVlKSwgY29sb3IgPSBjb3VudHJ5KSkrCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gZHJhdyksIGFscGhhID0gMC4wMikrCiAgICBnZW9tX3BvaW50KGRhdGEgPSBtb2RlbGxhYmxlX2RhdGEsIGFlcyh5ID0gdG90YWxfZGVhdGhzKSwKICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siLCBzaXplID0gMC41KSsKICAgIHNjYWxlX3lfbG9nMTAoKSsKICAgIGZhY2V0X3dyYXAofmNvdW50cnkpKwogICAgeGxhYigibmRheXMgc2luY2UgMTAgdG90YWwgZGVhdGhzIikrCiAgICB5bGFiKCJjdW11bGF0aXZlIGRlYXRocyAobG9nIHNjYWxlKSIpKwogICAgc2NhbGVfY29sb3JfZGlzY3JldGUoZ3VpZGUgPSAibm9uZSIsIGwgPSA1MCkrCiAgICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKVGhpcyBpcyBhIHBsb3Qgb2YgZGFpbHkgZXN0aW1hdGVkIHJhdGUgb2YgaW5jcmVhc2Ugb2YgdG90YWwgZGVhdGhzIChkb3duIGlzIGdvb2QpLgo8ZGl2IHN0eWxlPSJ3aWR0aDoxMDAlIj4KPGRpdiBzdHlsZT0id2lkdGg6NzUlO21hcmdpbjphdXRvOyI+CmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG8gPSBGfQpib290c3RyYXBzICU+JQogIG11dGF0ZShuZGF5cyA9IGFzLm51bWVyaWMobmRheXMpKSAlPiUKICBncm91cF9ieShjb3VudHJ5LCBuZGF5cyklPiUKICBzdW1tYXJpc2UocmF0ZSA9IG1lZGlhbihyYXRlKSklPiUKICBmaWx0ZXIoaXMuZmluaXRlKHJhdGUpKSU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoY291bnRyeTIgPSBjb3VudHJ5LAogICAgICAgICBjb3VudHJ5ID0gTlVMTCktPnJhdGVfdHJlbmRzCgoKYm9vdHN0cmFwcyAlPiUKICB1bmdyb3VwKCkgJT4lCiAgZ2dwbG90KGFlcyhhcy5udW1lcmljKG5kYXlzKSwgcmF0ZSwgY29sb3IgPSBjb3VudHJ5KSkrCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLjMsIGxpbmV0eXBlID0gMikrCiAgICBnZW9tX2xpbmUoZGF0YSA9IHJhdGVfdHJlbmRzLAogICAgICAgICAgICAgIGFlcyhncm91cCA9IGNvdW50cnkyKSwKICAgICAgICAgICAgICBjb2xvciA9ICJncmV5NjAiKSsKICAgIGdlb21fdmxpbmUoZGF0YSA9IGxvY2tkb3ducywgYWVzKHhpbnRlcmNlcHQgPSBuZGF5cyksCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMSwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTMwIikrCiAgICBnZW9tX3ZsaW5lKGRhdGEgPSBsb2NrZG93bnMsIGFlcyh4aW50ZXJjZXB0ID0gbmRheXMrMTQpLAogICAgICAgICAgICAgICBsaW5ldHlwZSA9IDMsCiAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkzMCIpKwogICAgZ2VvbV9saW5lKGFlcyhncm91cCA9IGRyYXcpLCBhbHBoYSA9IDAuMDIpKwogICAgZmFjZXRfd3JhcCh+Y291bnRyeSkrCiAgICBzY2FsZV95X2xvZzEwKCJjdW11bGF0aXZlIGRlYXRoIGRhaWx5IGluY3JlYXNlIHJhdGUgKGxvZyBzY2FsZSkiLAogICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBOVUxMKSsKICAgIHhsaW0oMSw0MCkrCiAgICB4bGFiKCJuZGF5cyBzaWNlIDEwIHRvdGFsIGRlYXRocyIpKwogICAgc2NhbGVfY29sb3JfZGlzY3JldGUoZ3VpZGUgPSAibm9uZSIsIGwgPSA1MCkrCiAgICB0aGVtZV9taW5pbWFsKCkrCiAgICBnZ3RpdGxlKCJkYWlseSByYXRlIG9mIGluY3JlYXNlIikKYGBgCgo8L2Rpdj4KPC9kaXY+CgpUaGlzIGlzIGEgcGxvdCBvZiBkYWlseSBlc3RpbWF0ZWQgJ2RvdWJsaW5nIHJhdGUnICh1cCBpcyBnb29kKS4KPGRpdiBzdHlsZT0id2lkdGg6MTAwJSI+CjxkaXYgc3R5bGU9IndpZHRoOjc1JTttYXJnaW46YXV0bzsiPgpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvID0gRn0KCmJvb3RzdHJhcHMgJT4lCiAgbXV0YXRlKGRvdWJsaW5nID0gbG9nKDIsIGJhc2UgPSByYXRlKSwKICAgICAgICAgbmRheXMgPSBhcy5udW1lcmljKG5kYXlzKSkgJT4lCiAgZ3JvdXBfYnkoY291bnRyeSwgbmRheXMpJT4lCiAgc3VtbWFyaXNlKGRvdWJsaW5nID0gbWVkaWFuKGRvdWJsaW5nKSklPiUKICBmaWx0ZXIoaXMuZmluaXRlKGRvdWJsaW5nKSklPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKGNvdW50cnkyID0gY291bnRyeSwKICAgICAgICAgY291bnRyeSA9IE5VTEwpLT5kb3VibGluZ190cmVuZHMKCmJvb3RzdHJhcHMgJT4lCiAgbXV0YXRlKGRvdWJsaW5nID0gbG9nKDIsIGJhc2UgPSByYXRlKSwKICAgICAgICAgbmRheXMgPSBhcy5udW1lcmljKG5kYXlzKSkgJT4lCiAgI2ZpbHRlcihuZGF5cyA8PSAzMCklPiUKICBnZ3Bsb3QoYWVzKGFzLm51bWVyaWMobmRheXMpLCBkb3VibGluZywgY29sb3IgPSBjb3VudHJ5KSkrCiAgICBnZW9tX2xpbmUoZGF0YSA9IGRvdWJsaW5nX3RyZW5kcywgCiAgICAgICAgICAgICAgYWVzKGdyb3VwID0gY291bnRyeTIpLAogICAgICAgICAgICAgIGNvbG9yID0gImdyZXk2MCIpKwogICAgZ2VvbV92bGluZShkYXRhID0gbG9ja2Rvd25zLCBhZXMoeGludGVyY2VwdCA9IG5kYXlzKSwgCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMSwgCiAgICAgICAgICAgICAgIGNvbG9yID0gImdyZXkzMCIpKwogICAgZ2VvbV92bGluZShkYXRhID0gbG9ja2Rvd25zLCBhZXMoeGludGVyY2VwdCA9IG5kYXlzKzE0KSwgCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMywKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTMwIikrICAKICAgIGdlb21fbGluZShhZXMoZ3JvdXAgPSBkcmF3KSwgYWxwaGEgPSAwLjAyKSsKICAgIGZhY2V0X3dyYXAofmNvdW50cnkpKwogICAgc2NhbGVfeV9sb2cxMCgiJ2RvdWJsaW5nJyByYXRlIGV2ZXJ5IFkgZGF5cyIsCiAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMSwgMywgNywgMTQsIDMwLCA2MCksCiAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMSwgNjApLAogICAgICAgICAgICAgICAgICBtaW5vcl9icmVha3MgPSBOVUxMKSsKICAgIHhsaW0oMSw0MCkrCiAgICB4bGFiKCJuZGF5cyBzaWNlIDEwIHRvdGFsIGRlYXRocyIpKwogICAgc2NhbGVfY29sb3JfZGlzY3JldGUoZ3VpZGUgPSAibm9uZSIsIGwgPSA1MCkrCiAgICB0aGVtZV9taW5pbWFsKCkrCiAgICBnZ3RpdGxlKCJkYWlseSAnZG91YmxpbmcgcmF0ZScgZXN0aW1hdGUiKQpgYGAKPC9kaXY+CjwvZGl2PgojIFNlbGYgT2JzZXNzaW9uCgo8ZGl2IHN0eWxlPSJ3aWR0aDoxMDAlIj4KPGRpdiBzdHlsZT0id2lkdGg6NzUlO21hcmdpbjphdXRvOyI+CmBgYHtyIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGVjaG8gPSBGfQpib290c3RyYXBzICU+JQogIHVuZ3JvdXAoKSAlPiUKICBmaWx0ZXIoY291bnRyeSAlaW4lIGMoInVuaXRlZF9zdGF0ZXNfb2ZfYW1lcmljYSIsICJ1bml0ZWRfa2luZ2RvbSIsICJpdGFseSIsCiAgICAgICAgICAgICAgICAgICAgICAgICJmcmFuY2UiLCAiY2hpbmEiLCAic3BhaW4iKSklPiUKICBnZ3Bsb3QoYWVzKGFzLm51bWVyaWMobmRheXMpLCByYXRlLCBjb2xvciA9IGNvdW50cnkpKSsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMywgbGluZXR5cGUgPSAyKSsKICAgIGdlb21fbGluZShkYXRhID0gcmF0ZV90cmVuZHMsCiAgICAgICAgICAgICAgYWVzKGdyb3VwID0gY291bnRyeTIpLAogICAgICAgICAgICAgIGNvbG9yID0gImdyZXk2MCIpKwogICAgZ2VvbV92bGluZShkYXRhID0gbG9ja2Rvd25zLCBhZXMoeGludGVyY2VwdCA9IG5kYXlzKSwKICAgICAgICAgICAgICAgbGluZXR5cGUgPSAxLAogICAgICAgICAgICAgICBjb2xvciA9ICJncmV5MzAiKSsKICAgIGdlb21fdmxpbmUoZGF0YSA9IGxvY2tkb3ducywgYWVzKHhpbnRlcmNlcHQgPSBuZGF5cysxNCksCiAgICAgICAgICAgICAgIGxpbmV0eXBlID0gMywKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTMwIikrCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gZHJhdyksIGFscGhhID0gMC4wMikrCiAgICBmYWNldF93cmFwKH5jb3VudHJ5KSsKICAgIHNjYWxlX3lfbG9nMTAoImN1bXVsYXRpdmUgZGVhdGggZGFpbHkgaW5jcmVhc2UgcmF0ZSAobG9nIHNjYWxlKSIsCiAgICAgICAgICAgICAgICAgIG1pbm9yX2JyZWFrcyA9IE5VTEwpKwogICAgeGxpbSgxLDQwKSsKICAgIHhsYWIoIm5kYXlzIHNpY2UgMTAgdG90YWwgZGVhdGhzIikrCiAgICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShndWlkZSA9ICJub25lIiwgbCA9IDUwKSsKICAgIHRoZW1lX21pbmltYWwoKSsKICAgIGdndGl0bGUoImRhaWx5IHJhdGUgb2YgaW5jcmVhc2UiKQpgYGAKPC9kaXY+CjwvZGl2Pg==